import { eachArray, EachReturn } from './utils'

interface ObsreverItem {
  observer: MutationObserver
  option: MutationObserverInit
  callback: MutationCallback
  connect: boolean
}

interface ObserverAttributeOption {
  subtree?: boolean
  attributeFilter?: string[]
}

type MO_NAMESPACE_DATA = Map<string, ObsreverItem[]>

export const MO_NAMESPACE_DEFAULT = '*'

const MUTATION_OBSERVER_TEMP: WeakMap<HTMLElement, MO_NAMESPACE_DATA> = new WeakMap()

Reflect.set(window, 'MUTATION_OBSERVER_TEMP', MUTATION_OBSERVER_TEMP)

/**
 * 初始化 MutationObserver 及相关配置
 * @param target HTMLElement
 * @param callback MutationCallback
 * @param option MutationObserverInit
 * @param ns 命名空间
 */
export function moObserve<T extends HTMLElement>(
  target: T,
  callback: MutationCallback,
  option: MutationObserverInit,
  ns: string = MO_NAMESPACE_DEFAULT
) {
  let temp = MUTATION_OBSERVER_TEMP.get(target)
  if (!temp) {
    MUTATION_OBSERVER_TEMP.set(target, (temp = new Map()))
  }
  let item = temp.get(ns)
  if (!item) {
    temp.set(ns, (item = []))
  }
  const observer = new MutationObserver(callback)
  item.push({ observer, callback, option, connect: false })
}

/**
 * 初始化 MutationObserver 及相关配置，仅监听 attribute 变化
 * @param target
 * @param callback
 * @param option
 * @param ns 命名空间
 */
export function moAttribute<T extends HTMLElement>(
  target: T,
  callback: MutationCallback,
  option: ObserverAttributeOption | null = null,
  ns: string = MO_NAMESPACE_DEFAULT
) {
  let config = { attribute: true, attributeOldValue: true }
  option && (config = Object.assign(config, option))
  moObserve(target, callback, config, ns)
}

/**
 * 初始化 MutationObserver 及相关配置，仅监听 childList 变化
 * @param target
 * @param callback
 * @param subtree
 * @param ns 命名空间
 */
export function moChildList<T extends HTMLElement>(
  target: T,
  callback: MutationCallback,
  subtree: boolean = false,
  ns: string = MO_NAMESPACE_DEFAULT
) {
  moObserve(target, callback, { childList: true, subtree: !!subtree }, ns)
}

/**
 * 初始化 MutationObserver 及相关配置，仅监听 character 变化
 * @param target
 * @param callback
 * @param ns 命名空间
 */
export function moCharacter<T extends HTMLElement>(target: T, callback: MutationCallback, ns: string = MO_NAMESPACE_DEFAULT) {
  moObserve(target, callback, { characterData: true, characterDataOldValue: true, subtree: true }, ns)
}

function moConnectNS<T extends HTMLElement>(target: T, items: ObsreverItem[]) {
  items.forEach((item) => {
    if (!item.connect) {
      item.observer.observe(target, item.option)
      item.connect = true
    }
  })
}

/**
 * 进行 MutationObserver 绑定
 * @param target HTMLElement
 * @param ns 命名空间
 */
export function moConnect<T extends HTMLElement>(target: T, ns?: string) {
  const temp = MUTATION_OBSERVER_TEMP.get(target)
  if (temp) {
    if (ns) {
      // 只对指定的命名空间进行监听绑定
      moConnectNS(target, temp.get(ns) || [])
    } else {
      // 对所有命名空间下的监听器进行绑定
      temp.forEach((items) => {
        moConnectNS(target, items)
      })
    }
  }
}

function moDisconnectNS(items: ObsreverItem[]) {
  items.forEach((item) => {
    if (item.connect) {
      item.observer.disconnect()
      item.connect = false
    }
  })
}

/**
 * 取消 MutationObserver 绑定
 * @param target HTMLElement
 * @param ns 命名空间
 */
export function moDisconnect<T extends HTMLElement>(target: T, ns?: string) {
  const temp = MUTATION_OBSERVER_TEMP.get(target)
  if (temp) {
    if (ns) {
      // 只对指定的命名空间进行监听绑定
      moDisconnectNS(temp.get(ns) || [])
    } else {
      // 对所有命名空间下的监听器进行绑定
      temp.forEach((items) => {
        moDisconnectNS(items)
      })
    }
  }
}

function moRemoveNS(items: ObsreverItem[], callback: MutationCallback) {
  eachArray(items, function (item) {
    if (item.callback === callback) {
      item.connect && item.observer.disconnect()
      return EachReturn.DELETE
    }
  })
}

/**
 * 取消绑定并移除相关配置
 * @param target HTMLElement
 * @param callback MutationCallback
 * @param ns 命名空间
 */
export function moRemove<T extends HTMLElement>(target: T, callback: MutationCallback, ns?: string) {
  let temp = MUTATION_OBSERVER_TEMP.get(target)
  if (temp) {
    if (ns) {
      // 只对指定的命名空间进行监听绑定
      moRemoveNS(temp.get(ns) || [], callback)
    } else {
      // 对所有命名空间下的监听器进行绑定
      temp.forEach((items) => {
        moRemoveNS(items, callback)
      })
    }
  }
}

function moClearNS(items: ObsreverItem[]) {
  items.forEach((item) => {
    item.connect && item.observer.disconnect()
  })
  items.length = 0
}

/**
 * 清除所有绑定并移除相关配置
 * @param target HTMLElement
 * @param ns 命名空间
 */
export function moClear<T extends HTMLElement>(target: T, ns?: string) {
  const temp = MUTATION_OBSERVER_TEMP.get(target)
  if (temp) {
    if (ns) {
      // 只对指定的命名空间进行监听绑定
      moClearNS(temp.get(ns) || [])
    } else {
      // 对所有命名空间下的监听器进行绑定
      temp.forEach((items) => {
        moClearNS(items)
      })
    }
  }
}
